#include <2025Linux.h>
typedef struct ConnInfo_s {
    int netfd;
    int isConnected; // 0 未连接，1 已连接
    time_t lastActive; // 这个客户端的上次活跃时间
} ConnInfo_t;
int main(int argc, char *argv[])
{
    // ./1_server_chatroom 192.168.72.128 12345
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(sockfd,-1,"socket");
    int flag = 1;
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
    ERROR_CHECK(ret,-1,"setsockopt");
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    ret = bind(sockfd,(struct sockaddr *)&addr, sizeof(addr));
    ERROR_CHECK(ret,-1,"bind");
    ret = listen(sockfd, 10);
    ERROR_CHECK(ret,-1,"listen");

    int epfd = epoll_create(1);//申请内核数据结构
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = sockfd; //在就绪以后，会把fd填入就绪队列
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event);
    // 设计一个数据结构数组，去管理所有的客户端的信息
    ConnInfo_t client[1024];
    for(int i = 0; i < 1024; ++i){
        client[i].isConnected = 0;
    }
    int fdToidx[1024]; //哈希表 已知netfd 求client的下标i
    for(int netfd = 0; netfd < 1024; ++netfd){
        fdToidx[netfd] = -1; //每个netfd都没有在client数组当中
    }
    int curidx = 0; //下一个客户端在client数组中的下标
    char buf[4096];
    struct epoll_event readyset[1024];
    while(1){
        int readynum = epoll_wait(epfd,readyset,1024,1000); // 1000ms --> 1s
        time_t now = time(NULL);
        printf("select return, now = %s\n", ctime(&now));
        for(int i = 0; i < readynum; ++i){//使用epoll和select的最大区别
            if(readyset[i].data.fd == sockfd){
                // sockfd就绪，有新客户端接入
                // 往client数组中加了一个成员
                client[curidx].netfd = accept(sockfd,NULL,NULL);//accept之后，sockfd需要继续监听着
                client[curidx].isConnected = 1; 
                client[curidx].lastActive = now; // 记录该连接的活跃时间
                printf("new client is connected! id = %d, netfd = %d\n",curidx, client[curidx].netfd);
                // 把新客户端的netfd加入监听
                event.events = EPOLLIN;
                event.data.fd = client[curidx].netfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,client[curidx].netfd,&event);
                // 调整fdToidx
                fdToidx[client[curidx].netfd] = curidx;
                ++curidx;
            }
            else{
                // 某个netfd就绪，旧客户端发消息or断开
                int netfd = readyset[i].data.fd; // 在epoll_wait返回值，无需for循环就可以知道是谁就绪
                memset(buf,0,sizeof(buf));
                ssize_t sret = recv(netfd,buf,sizeof(buf),0);
                if(sret == 0){
                    printf("client closed!\n");
                    epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,NULL);//取消监听
                    close(netfd);//断开连接
                    // 改isConnected 要根据netfd --> client的i ==> 哈希查找
                    int idx = fdToidx[netfd];
                    client[idx].isConnected = 0;
                    fdToidx[netfd] = -1; // 把数据从哈希表中移除
                    continue;
                }
                int idx = fdToidx[netfd]; 
                // 来消息了，更新一下活跃时间
                client[idx].lastActive = now;
                // 转发
                for(int j = 0; j < curidx; ++j){
                    if(client[j].isConnected == 1 && j != idx){
                        send(client[j].netfd,buf,strlen(buf),0);
                    }
                }
            }
        }//最外面的for循环，更新了所有的lastActive
        //开始踢人
        for(int i = 0; i < curidx; ++i){
            if(client[i].isConnected == 1 && now - client[i].lastActive > 10){
                epoll_ctl(epfd,EPOLL_CTL_DEL,client[i].netfd,NULL);
                close(client[i].netfd); // 断开连接
                client[i].isConnected = 0; //修改client数组内容
                fdToidx[client[i].netfd] = -1; //修改相关的哈希表
            }
        }
    }
    return 0;
}

